SQL Overview

SQL (Structured Query Language) is the standard language used to define, query, and manage data in relational database management systems (RDBMSs). It is grounded in relational algebra and tuple relational calculus, giving SQL a precise mathematical meaning.

In practice, SQL powers everything from small embedded apps (e.g., SQLite) to large-scale enterprise and cloud data platforms (e.g., PostgreSQL, MySQL/MariaDB, SQL Server, Oracle, and cloud-native systems).

SQL is the common interface between applications and persistent relational data. Once you master the core of SQL, you can transfer skills across most database systems.

🧠 Core idea: SQL is declarative (“what”, not “how”)

SQL is a declarative language: you specify what result you want, and the DBMS decides how to compute it efficiently. This design is crucial because the DBMS can change execution strategies automatically as data sizes, indexes, and statistics change.

  • You describe the desired result set.
  • The DBMS chooses access paths (index scan vs sequential scan).
  • The DBMS chooses join order and join algorithms (nested-loop, hash join, sort-merge).
  • The DBMS may use parallelism for speedups on multi-core systems.
🧩 SQL command families (DDL, DML, TCL, DCL)
  • DDL (Data Definition Language) — define structure: CREATE, ALTER, DROP, CREATE INDEX
  • DML (Data Manipulation Language) — query & modify data: SELECT, INSERT, UPDATE, DELETE, MERGE
  • TCL (Transaction Control Language) — transaction boundaries: COMMIT, ROLLBACK, SAVEPOINT
  • DCL (Data Control Language) — permissions: GRANT, REVOKE
Some sources separate DQL (query language) from DML. In most DBMS documentation and practice, SELECT is treated as part of DML.
Types of SQL Commands
Figure: SQL command categories (DDL/DML/TCL/DCL). (Attribution: GeeksforGeeks article on SQL command groups.)
🕰️ Brief history and modern SQL
  • 1970
    Codd introduces the relational model (foundation of relational databases).
  • 1970s
    IBM System R prototypes early SQL-style querying (historically influenced by SEQUEL).
  • 1986–1987
    SQL becomes an ANSI standard and is adopted by ISO.
  • 1992
    SQL-92 expands joins, constraints, and core relational features.
  • 1999+
    Recursive queries, triggers, advanced constraints, and analytics capabilities emerge.
  • 2016
    SQL:2016 strengthens SQL/JSON support for semi-structured data.
  • 2023
    SQL:2023 is published, adding more JSON capabilities and a new standard component for SQL Property Graph Queries (SQL/PGQ).
  • Today
    SQL remains central for OLTP, analytics, and pipelines—now spanning relational tables, JSON documents, and graph-style querying (DBMS support varies by vendor).
What “modern SQL” means in 2025
  • Analytics: window functions, CTEs, recursive queries.
  • Semi-structured: JSON types + JSON path/query functions (vendor-dependent).
  • Scale: partitioning, parallel execution, columnar storage/engines in some systems.
  • Beyond relational: graph querying is entering the standard (SQL/PGQ), but adoption is uneven.
⚖️ Standard SQL vs vendor SQL (what you should expect)

Although SQL is standardized (ISO/IEC 9075), no DBMS implements the entire standard. Instead, systems share a large common core and differ in optional features and extensions.

  • PostgreSQL: strong standards compliance; powerful extensions; excellent JSON support.
  • MySQL / MariaDB: widely used in web stacks; feature sets differ by version and engine.
  • SQL Server: rich tooling; strong analytics and enterprise integration.
  • Oracle: enterprise features; extensive optimizer and scalability support.
  • SQLite: lightweight embedded DB; great for mobile and local applications.
Practical takeaway for this course

Master core SQL first (tables, keys, joins, grouping, constraints, transactions). Then learn vendor-specific details (JSON syntax, pagination, MERGE variants, date/time functions) when needed for a particular platform.

🧬 SQL Data Types (overview and portability)

SQL provides a rich set of data types to represent numbers, text, dates/times, logical values, and binary objects. While the SQL standard defines broad categories, exact support and naming may vary across database vendors.

Numeric data types

Category Standard examples Description Example values
Integer INTEGER, SMALLINT, BIGINT Exact whole numbers -3, 0, 42
Exact decimal DECIMAL(p,s), NUMERIC(p,s) Exact fixed-point numbers (recommended for money) 10.75, -0.01
Approximate REAL, FLOAT, DOUBLE PRECISION Floating-point numbers (rounding may occur) 3.14, -0.0001

Character and binary data types

Category Standard examples Description Example values
Fixed-length text CHAR(n) Strings padded to fixed length 'CSCI'
Variable-length text VARCHAR(n) Most commonly used string type 'Database'
Large text TEXT, CLOB Long-form text documents 'Lorem ipsum…'
Fixed-length binary BINARY(n) Binary bytes with fixed size 0xAF12
Variable-length binary VARBINARY(n), BLOB Images, audio, files (binary data)

Date and time data types

Standard type Description Example values
DATE Calendar date (year, month, day) 2025-03-10
TIME Time of day (no date) 14:30:00
TIMESTAMP Date and time (optionally with time zone) 2025-03-10 14:30:00
TIMESTAMP is not a Unix integer by definition. It is a structured date–time type in SQL. Some systems allow conversion to/from Unix epoch seconds, but that is vendor-specific.

Boolean and special-purpose types

Type Description Notes
BOOLEAN Logical true/false values Some systems emulate using integers
ENUM Predefined set of values Common in MySQL; not part of core SQL standard
SET Multiple selected values Vendor-specific (e.g., MySQL)
JSON Semi-structured JSON documents Standardized in SQL:2016+

Not all SQL data types are supported uniformly across database vendors. Names, limits, and behavior may differ.

The data types listed here are the most commonly used. Real systems often provide additional vendor-specific types (e.g., MONEY in SQL Server, spatial types, UUIDs).

Each DBMS enforces its own maximum size limits for strings, binary objects, and numeric precision. Always consult vendor documentation when designing schemas.

Exercise (medium): Portability across DBMSs
Medium

Give two reasons why the same SQL query might behave differently across different database management systems.

Show hint

Consider differences in SQL standard support, vendor extensions, and internal query optimization strategies.

Show answer

The behavior can differ because:

  • DBMSs support different subsets of the SQL standard and provide vendor-specific extensions (e.g., JSON functions, date/time handling).
  • Query optimizers use different cost models, statistics, and default settings (e.g., transaction isolation levels).

MySQL Overview

MySQL is a widely used relational database management system (RDBMS). It is open-source (community edition) with commercial offerings and support from Oracle.

Terminology you must get right (MySQL-specific)
  • MySQL Server: the running DBMS process/service that accepts connections and executes SQL.
  • Database (≈ schema in MySQL): a logical namespace/container that groups tables, views, etc.
  • Table: a relation (rows/columns) stored inside a database.
In MySQL, the words database and schema are commonly used interchangeably.
Visualization: MySQL as a client–server system

Most SQL work happens in a client–server model: a client sends SQL; the server parses, optimizes, executes, and returns rows.

Client
  • mysql CLI
  • MySQL Workbench
  • Python connector
  • Java JDBC
  • Web apps / APIs
MySQL Server
  • Parser & validator
  • Query rewriter
  • Optimizer
  • Execution engine
  • Storage engine (e.g., InnoDB)

This separation explains why credentials, network ports, and server status matter: your SQL is executed by the server, not your laptop.

Show a concrete example

When you run SELECT * FROM student; in a client, the SQL text is sent to the server. The server reads data pages (via the storage engine), computes the result, and sends rows back to the client.

Key idea: Storage engines (InnoDB is the default)

MySQL is architected with a SQL layer on top of storage engines. The default engine is InnoDB, which supports transactions (ACID), row-level locking, crash recovery, and foreign keys.

Quick comparison: InnoDB vs legacy alternatives
InnoDB (default)
  • Transactions (ACID)
  • Row-level locking
  • Foreign keys
  • Crash recovery
MyISAM (legacy/limited)
  • No transactions
  • Table-level locking
  • No foreign keys
  • Use only for special legacy cases

For most modern applications and coursework, assume InnoDB unless explicitly stated otherwise.

Key features
  • Core relational SQL: joins, grouping/aggregation, constraints, indexes.
  • Transactions (InnoDB): COMMIT/ROLLBACK, isolation concepts.
  • Modern SQL features (MySQL 8+): common table expressions (CTEs) and window functions.
  • JSON support: MySQL supports a native JSON type (introduced in 5.7 and expanded in 8.x).
  • Security basics: authentication, privileges (GRANT/REVOKE), TLS support (configuration-dependent).
🕰️ Brief history and today’s release model (updated)
  • 1995
    MySQL is created by MySQL AB.
  • 2008
    MySQL AB is acquired by Sun Microsystems.
  • 2010
    Oracle acquires Sun Microsystems (and MySQL).
  • 2015
    MySQL 5.7 GA era (includes native JSON type).
  • 2018
    MySQL 8.0 GA era (CTEs, window functions, many SQL enhancements).
  • 2020
    HeatWave is introduced as an in-memory accelerator in Oracle’s MySQL cloud service.
  • 2024+
    Oracle introduces a new model: LTS (e.g., MySQL 8.4 LTS) and Innovation releases (e.g., MySQL 9.0.x).

“LTS (Long-Term Support)” is what many production systems prefer (stable, long support). Innovation releases deliver new features faster. For this course, we focus on portable SQL concepts; version details matter mainly for feature availability.

Concept checks
Exercise (easy): Server vs database vs table
Easy

In MySQL terminology, what is the difference between a server, a database (schema), and a table?

Show hint

Think: process/service, namespace/container, and relation (rows/columns).

Show answer

The server is the running DBMS service. A database (schema) is a logical container/namespace inside the server. A table is a relation (rows/columns) stored within a database.

Exercise (medium): Why does InnoDB matter?
Medium

Name two features you get from using the InnoDB storage engine that you would not reliably get from a non-transactional engine.

Show hint

Think about transactions, locking granularity, crash recovery, and foreign keys.

Show answer

Examples: (1) ACID transactions (COMMIT/ROLLBACK), (2) row-level locking for better concurrency, (3) crash recovery, (4) foreign key enforcement.

Exercise (hard): Choosing a release line
Hard

You are deploying a campus application that must be stable for years. Would you prefer an LTS release line or an Innovation release line? Explain your choice in 2–3 sentences.

Show hint

Think about stability/support windows vs new features delivered faster.

Show answer

Prefer LTS for long-lived stable deployments because it emphasizes stability and longer support. Innovation releases are better when you need new features quickly and can tolerate faster change.

🧬 MySQL data types

Numeric

TypeDescriptionNotes / Example
TINYINT / SMALLINT / INT / BIGINTExact integersUse UNSIGNED if values are never negative
DECIMAL(p,s)Exact fixed-pointBest for currency: DECIMAL(10,2)
FLOAT / DOUBLEApproximate floating pointFast but can round; avoid for money
BIT(n)Bit-fieldFlags/bitmasks; n up to 64
BOOLEANLogical valuesIn MySQL, BOOLEAN is a synonym for TINYINT(1)

String & Binary

TypeDescriptionNotes / Example
CHAR(n)Fixed-length stringGood for fixed codes (e.g., state abbreviations)
VARCHAR(n)Variable-length stringMost common general-purpose string
TEXT / MEDIUMTEXT / LONGTEXTLarge textPick size based on expected max length
BINARY(n) / VARBINARY(n)Binary bytesBinary identifiers, hashed values
BLOB familyBinary large objectsFiles, images, audio (often better stored externally + keep URLs)
ENUM / SETEnumerated valuesConvenient but less portable across DBMSs

Date & Time

TypeDescriptionCorrect example
DATEDate only'2025-12-12'
TIMETime only'14:30:00'
DATETIMEDate + time (no timezone)'2025-12-12 14:30:00'
TIMESTAMPDate + time with timezone/UTC semantics'2025-12-12 14:30:00'
YEARYear2025

Other

TypeDescriptionNotes
JSONJSON documentsUseful for semi-structured data; indexing/querying differs from relational design
GEOMETRYSpatial dataPoints/lines/polygons; requires spatial indexes for performance

Installing and Starting MySQL

In this course, you will typically run a MySQL Server on your machine (or a VM) and connect to it using a client such as the mysql command-line tool or MySQL Workbench. The most common setup issues are simply: the server is not running, or the connection parameters are incorrect.

Installation checklist
Before you debug anything, confirm these
  • Confirm server is running: service status / Task Manager / brew services.
  • Know your connection parameters: host, port (default 3306), username, password.
  • Pick a GUI: Workbench or a lightweight SQL client.
  • Create a sandbox database for practice.
  • Use InnoDB (transactional, FK constraints).
🎬 Installation videos: MySQL on Windows, Ubuntu, and macOS

MySQL on Windows 10
Install MySQL 8.0 Server and MySQL Workbench.

MySQL on Ubuntu 22.04
Install and configure MySQL Server on Ubuntu Linux.

MySQL on macOS
Install MySQL Community Server using the macOS installer.

📖 Official docs + macOS installation steps

Refer to the official website for details. The following steps are only for the installation of MySQL on macOS.

Step 1: Download MySQL Server (Community Edition) for macOS.

Step 2: Click the downloaded .dmg file to open the MySQL Server installer package. During the installation, it will display a “Configure MySQL Server” dialog; set the root user password and click Finish to complete the installation.

Step 3: Enable the launched service: open macOS System Preferences, select the MySQL preference panel, and click Start MySQL Server.

MySQL Preference Pane
MySQL preference pane (Start/Stop the MySQL Server).

Step 4: Connect to the MySQL server by running mysql -u root -p. You can disconnect by entering exit. Note that the MySQL server is still running in the background.

(base) wtong@wt-m2 ~ % mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.32 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> EXIT
Bye
(base) wtong@wt-m2 ~ %
“Access denied” debugging

You run mysql -u root -p and get Access denied for user. List three likely causes and fixes.

Show hint

Credentials, host restrictions, and server status are the big three.

Show answer
  • Wrong password → reset / verify credentials.
  • Wrong user/host mapping → try -h localhost or create user with correct host.
  • Server not running / wrong port → start MySQL service or specify correct port.
🧯 Notes and common problems

Common problem 1: How to fix “mysql command not found”

Common problem 2: If you forget the root password, you can use the following steps on macOS.

  1. Make sure you have stopped MySQL first.
    Go to: 'System Preferences' >> 'MySQL' and stop MySQL
    OR,
    sudo /usr/local/mysql/support-files/mysql.server start
    sudo /usr/local/mysql/support-files/mysql.server stop
    sudo /usr/local/mysql/support-files/mysql.server status
  2. Run the server in safe mode with privilege bypass: sudo mysqld_safe --skip-grant-tables
  3. In a new window connect to the database, set a new password, flush permissions, and quit:
    mysql -u root
    UPDATE mysql.user SET authentication_string=null WHERE User='root';
    FLUSH PRIVILEGES;
    exit;
    Then:
    mysql -u root
    ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'yourpasswd';

Play with MySQL Commands

This subsection is a hands-on “muscle memory” guide for the mysql command-line client. You will practice connecting, creating databases/tables, loading data, and running basic queries. Note: examples may display an older version string; your installed version may differ. As of 2025, MySQL has both an LTS track (e.g., 8.4.x) and an Innovation track (9.x).

🔌 Connecting to and disconnecting from the server

Think “client → server”. The mysql program is a client; mysqld is the server daemon. You must know host, port (default 3306), username, and password.

(base) wtong@wt-m2 ~ % mysql -h host -u user -p
Enter password: *******

host and user represent the host name where your MySQL server is running and the user name of your MySQL account. If you are logging in on the same machine that MySQL is running on, you can omit the host:

(base) wtong@wt-m2 ~ % mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.32 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> QUIT
Bye
(base) wtong@wt-m2 ~ %
Show tip: useful mysql client commands
  • \s — status summary (server, user, database, charset).
  • \u dbname — switch database (same as USE dbname).
  • \G — show result vertically (great for wide rows).
  • \c — cancel current input.
  • EXIT / QUIT — leave the client (server keeps running).
📝 Entering queries
mysql> SELECT VERSION(), CURRENT_DATE;
+-----------+--------------+
| VERSION() | CURRENT_DATE |
+-----------+--------------+
| 8.0.32    | 2023-09-07   |
+-----------+--------------+
1 row in set (0.00 sec)

mysql>
  • A query normally consists of an SQL statement followed by a semicolon. (There are exceptions where a semicolon may be omitted. QUIT is one of them.)

  • When you issue a query, mysql sends it to the server for execution and displays the results, then prints another mysql> prompt.

  • Output is shown in tabular form (rows and columns). Column labels usually come from selected columns (or the expression text if you selected an expression).

  • The client shows the number of returned rows and the elapsed time. This is wall-clock time and depends on load and latency.

Keywords may be entered in any letter case. The following are equivalent:

mysql> SELECT VERSION(), CURRENT_DATE;
mysql> select version(), current_date;
mysql> SeLeCt vErSiOn(), current_DATE;
🗂️ Create / delete databases
mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
| WT                 |
+--------------------+
6 rows in set (0.00 sec)
mysql> CREATE DATABASE helloworld;
Query OK, 1 row affected (0.00 sec)

mysql> SHOW DATABASES
    -> ;
+--------------------+
| Database           |
+--------------------+
| helloworld         |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
| WT                 |
+--------------------+
7 rows in set (0.00 sec)
mysql> DROP DATABASE helloworld;
Query OK, 0 rows affected (0.02 sec)

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
| WT                 |
+--------------------+
6 rows in set (0.01 sec)
Show tip: safe deletion

In real systems, avoid dropping databases casually. Prefer backups, snapshots, and least-privilege accounts.

🏗️ Select a database and create a table
Use a database
mysql> CREATE DATABASE helloworld;
Query OK, 1 row affected (0.00 sec)

mysql> USE helloworld
Database changed
mysql> SHOW TABLES;
Empty set (0.00 sec)

/* USE, like QUIT, does not require a semicolon.
   (You can terminate such statements with a semicolon if you like; it does no harm.)
*/
mysql> CREATE TABLE pet (name VARCHAR(20), owner VARCHAR(20),
    ->        species VARCHAR(20), sex CHAR(1), birth DATE, death DATE);
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW TABLES;
+----------------------+
| Tables_in_helloworld |
+----------------------+
| pet                  |
+----------------------+
1 row in set (0.00 sec)

mysql> DESCRIBE pet;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| name    | varchar(20) | YES  |     | NULL    |       |
| owner   | varchar(20) | YES  |     | NULL    |       |
| species | varchar(20) | YES  |     | NULL    |       |
| sex     | char(1)     | YES  |     | NULL    |       |
| birth   | date        | YES  |     | NULL    |       |
| death   | date        | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
Show note: why InnoDB matters

In modern MySQL, InnoDB is the default and recommended storage engine: it supports transactions, crash recovery, and foreign keys (when used correctly).

📥 Loading local files (LOCAL INFILE) — powerful but security-sensitive

To follow the examples exactly, you can download the sample data file here: pet.txt (save it locally and update the path in LOAD DATA LOCAL INFILE if needed).

mysql> SHOW VARIABLES LIKE 'local_infile';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| local_infile  | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

mysql> SET GLOBAL local_infile = 1;
Query OK, 0 rows affected (0.00 sec)

mysql> EXIT;

The command SET GLOBAL local_infile = 1; enables the server to accept LOAD DATA LOCAL INFILE statements. This is often disabled by default for security reasons.

  • SET GLOBAL: Changes the global server setting (usually requires admin privilege). It may revert after restart unless configured in the server configuration.
  • local_infile: Controls server-side acceptance of local file loading.
  • = 1: Enables it (server side). Some clients also require enabling it explicitly.
Show important security note (recommended reading)

LOAD DATA LOCAL INFILE can be abused when combined with SQL injection or misconfigured clients. Only enable it when needed, and prefer enabling it per-session/client invocation when possible.

Reconnect to MySQL with local infile enabled on the client side:

% mysql --local-infile=1 -u root -p
mysql> LOAD DATA LOCAL INFILE '/Users/wtong/Desktop/pet.txt' INTO TABLE pet;
Query OK, 1 row affected (0.01 sec)
Records: 1  Deleted: 0  Skipped: 0  Warnings: 0

mysql> SELECT * FROM pet;
+----------+-------+---------+------+------------+-------+
| name     | owner | species | sex  | birth      | death |
+----------+-------+---------+------+------------+-------+
| Whistler | Gwen  | bird    | NULL | 1997-12-09 | NULL  |
+----------+-------+---------+------+------------+-------+
1 row in set (0.01 sec)

mysql> INSERT INTO pet
    ->        VALUES ('Puffball','Diane','hamster','f','1999-03-30',NULL);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM pet;
+----------+-------+---------+------+------------+-------+
| name     | owner | species | sex  | birth      | death |
+----------+-------+---------+------+------------+-------+
| Whistler | Gwen  | bird    | NULL | 1997-12-09 | NULL  |
| Puffball | Diane | hamster | f    | 1999-03-30 | NULL  |
+----------+-------+---------+------+------------+-------+
2 rows in set (0.00 sec)

mysql> DELETE FROM pet;
Query OK, 2 rows affected (0.00 sec)

mysql> SELECT * FROM pet;
Empty set (0.00 sec)
🔎 Retrieving information from a table (SELECT basics)
Retrieving Information from a Table
SELECT what_to_select
FROM which_table
WHERE conditions_to_satisfy;
mysql> SELECT * FROM pet;
+----------+--------+---------+------+------------+------------+
| name     | owner  | species | sex  | birth      | death      |
+----------+--------+---------+------+------------+------------+
| Fluffy   | Harold | cat     | f    | 1993-02-04 | NULL       |
| Claws    | Gwen   | cat     | m    | 1994-03-17 | NULL       |
| Buffy    | Harold | dog     | f    | 1989-05-13 | NULL       |
| Fang     | Benny  | dog     | m    | 1990-08-27 | NULL       |
| Bowser   | Diane  | dog     | m    | 1979-08-31 | 1995-07-29 |
| Chirpy   | Gwen   | bird    | f    | 1998-09-11 | 0000-00-00 |
| Whistler | Gwen   | bird    | NULL | 1997-12-09 | 0000-00-00 |
| Slim     | Benny  | snake   | m    | 1996-04-29 | NULL       |
| Puffball | Diane  | hamster | f    | 1999-03-30 | NULL       |
+----------+--------+---------+------+------------+------------+
9 rows in set (0.00 sec)

mysql> UPDATE pet SET birth = '1989-08-31' WHERE name = 'Bowser';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM pet WHERE name = 'Bowser';
+--------+-------+---------+------+------------+------------+
| name   | owner | species | sex  | birth      | death      |
+--------+-------+---------+------+------------+------------+
| Bowser | Diane | dog     | m    | 1989-08-31 | 1995-07-29 |
+--------+-------+---------+------+------------+------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM pet WHERE birth >= '1998-1-1';
+----------+-------+---------+------+------------+------------+
| name     | owner | species | sex  | birth      | death      |
+----------+-------+---------+------+------------+------------+
| Chirpy   | Gwen  | bird    | f    | 1998-09-11 | 0000-00-00 |
| Puffball | Diane | hamster | f    | 1999-03-30 | NULL       |
+----------+-------+---------+------+------------+------------+
2 rows in set (0.00 sec)


mysql> SELECT * FROM pet WHERE species = 'dog' AND sex = 'f';
+-------+--------+---------+------+------------+-------+
| name  | owner  | species | sex  | birth      | death |
+-------+--------+---------+------+------------+-------+
| Buffy | Harold | dog     | f    | 1989-05-13 | NULL  |
+-------+--------+---------+------+------------+-------+


mysql> SELECT * FROM pet WHERE species = 'snake' OR species = 'bird';
+----------+-------+---------+------+------------+-------+
| name     | owner | species | sex  | birth      | death |
+----------+-------+---------+------+------------+-------+
| Chirpy   | Gwen  | bird    | f    | 1998-09-11 | NULL  |
| Whistler | Gwen  | bird    | NULL | 1997-12-09 | NULL  |
| Slim     | Benny | snake   | m    | 1996-04-29 | NULL  |
+----------+-------+---------+------+------------+-------+


mysql> SELECT * FROM pet WHERE (species = 'cat' AND sex = 'm')
       OR (species = 'dog' AND sex = 'f');
+-------+--------+---------+------+------------+-------+
| name  | owner  | species | sex  | birth      | death |
+-------+--------+---------+------+------------+-------+
| Claws | Gwen   | cat     | m    | 1994-03-17 | NULL  |
| Buffy | Harold | dog     | f    | 1989-05-13 | NULL  |
+-------+--------+---------+------+------------+-------+


mysql> SELECT name, birth FROM pet;
+----------+------------+
| name     | birth      |
+----------+------------+
| Fluffy   | 1993-02-04 |
| Claws    | 1994-03-17 |
| Buffy    | 1989-05-13 |
| Fang     | 1990-08-27 |
| Bowser   | 1989-08-31 |
| Chirpy   | 1998-09-11 |
| Whistler | 1997-12-09 |
| Slim     | 1996-04-29 |
| Puffball | 1999-03-30 |
+----------+------------+


mysql> SELECT DISTINCT owner FROM pet;
+--------+
| owner  |
+--------+
| Benny  |
| Diane  |
| Gwen   |
| Harold |
+--------+


mysql> SELECT name, birth FROM pet ORDER BY birth;
+----------+------------+
| name     | birth      |
+----------+------------+
| Buffy    | 1989-05-13 |
| Bowser   | 1989-08-31 |
| Fang     | 1990-08-27 |
| Fluffy   | 1993-02-04 |
| Claws    | 1994-03-17 |
| Slim     | 1996-04-29 |
| Whistler | 1997-12-09 |
| Chirpy   | 1998-09-11 |
| Puffball | 1999-03-30 |
+----------+------------+


mysql> SELECT name, birth FROM pet ORDER BY birth DESC;
+----------+------------+
| name     | birth      |
+----------+------------+
| Puffball | 1999-03-30 |
| Chirpy   | 1998-09-11 |
| Whistler | 1997-12-09 |
| Slim     | 1996-04-29 |
| Claws    | 1994-03-17 |
| Fluffy   | 1993-02-04 |
| Fang     | 1990-08-27 |
| Bowser   | 1989-08-31 |
| Buffy    | 1989-05-13 |
+----------+------------+


mysql> SELECT name, species, birth FROM pet
       ORDER BY species, birth DESC;
+----------+---------+------------+
| name     | species | birth      |
+----------+---------+------------+
| Chirpy   | bird    | 1998-09-11 |
| Whistler | bird    | 1997-12-09 |
| Claws    | cat     | 1994-03-17 |
| Fluffy   | cat     | 1993-02-04 |
| Fang     | dog     | 1990-08-27 |
| Bowser   | dog     | 1989-08-31 |
| Buffy    | dog     | 1989-05-13 |
| Puffball | hamster | 1999-03-30 |
| Slim     | snake   | 1996-04-29 |
+----------+---------+------------+


/* To determine how many years old each of your pets is, use the TIMESTAMPDIFF() function. */
mysql> SELECT name, birth, CURDATE(),
       TIMESTAMPDIFF(YEAR,birth,CURDATE()) AS age
       FROM pet;
+----------+------------+------------+------+
| name     | birth      | CURDATE()  | age  |
+----------+------------+------------+------+
| Fluffy   | 1993-02-04 | 2003-08-19 |   10 |
| Claws    | 1994-03-17 | 2003-08-19 |    9 |
| Buffy    | 1989-05-13 | 2003-08-19 |   14 |
| Fang     | 1990-08-27 | 2003-08-19 |   12 |
| Bowser   | 1989-08-31 | 2003-08-19 |   13 |
| Chirpy   | 1998-09-11 | 2003-08-19 |    4 |
| Whistler | 1997-12-09 | 2003-08-19 |    5 |
| Slim     | 1996-04-29 | 2003-08-19 |    7 |
| Puffball | 1999-03-30 | 2003-08-19 |    4 |
+----------+------------+------------+------+


mysql> SELECT name, birth, CURDATE(),
       TIMESTAMPDIFF(YEAR,birth,CURDATE()) AS age
       FROM pet ORDER BY age;
+----------+------------+------------+------+
| name     | birth      | CURDATE()  | age  |
+----------+------------+------------+------+
| Chirpy   | 1998-09-11 | 2003-08-19 |    4 |
| Puffball | 1999-03-30 | 2003-08-19 |    4 |
| Whistler | 1997-12-09 | 2003-08-19 |    5 |
| Slim     | 1996-04-29 | 2003-08-19 |    7 |
| Claws    | 1994-03-17 | 2003-08-19 |    9 |
| Fluffy   | 1993-02-04 | 2003-08-19 |   10 |
| Fang     | 1990-08-27 | 2003-08-19 |   12 |
| Bowser   | 1989-08-31 | 2003-08-19 |   13 |
| Buffy    | 1989-05-13 | 2003-08-19 |   14 |
+----------+------------+------------+------+

/* MySQL provides several functions for extracting parts of dates, such as YEAR(), MONTH(), and DAYOFMONTH(). */
mysql> SELECT name, birth, MONTH(birth) FROM pet;
+----------+------------+--------------+
| name     | birth      | MONTH(birth) |
+----------+------------+--------------+
| Fluffy   | 1993-02-04 |            2 |
| Claws    | 1994-03-17 |            3 |
| Buffy    | 1989-05-13 |            5 |
| Fang     | 1990-08-27 |            8 |
| Bowser   | 1989-08-31 |            8 |
| Chirpy   | 1998-09-11 |            9 |
| Whistler | 1997-12-09 |           12 |
| Slim     | 1996-04-29 |            4 |
| Puffball | 1999-03-30 |            3 |
+----------+------------+--------------+
/* To find names beginning with b:*/

mysql> SELECT * FROM pet WHERE name LIKE 'b%';
+--------+--------+---------+------+------------+------------+
| name   | owner  | species | sex  | birth      | death      |
+--------+--------+---------+------+------------+------------+
| Buffy  | Harold | dog     | f    | 1989-05-13 | NULL       |
| Bowser | Diane  | dog     | m    | 1989-08-31 | 1995-07-29 |
+--------+--------+---------+------+------------+------------+

/* To find names ending with fy: */
mysql> SELECT * FROM pet WHERE name LIKE '%fy';
+--------+--------+---------+------+------------+-------+
| name   | owner  | species | sex  | birth      | death |
+--------+--------+---------+------+------------+-------+
| Fluffy | Harold | cat     | f    | 1993-02-04 | NULL  |
| Buffy  | Harold | dog     | f    | 1989-05-13 | NULL  |
+--------+--------+---------+------+------------+-------+

/* To find names containing a w: */
mysql> SELECT * FROM pet WHERE name LIKE '%w%';
+----------+-------+---------+------+------------+------------+
| name     | owner | species | sex  | birth      | death      |
+----------+-------+---------+------+------------+------------+
| Claws    | Gwen  | cat     | m    | 1994-03-17 | NULL       |
| Bowser   | Diane | dog     | m    | 1989-08-31 | 1995-07-29 |
| Whistler | Gwen  | bird    | NULL | 1997-12-09 | NULL       |
+----------+-------+---------+------+------------+------------+

/* To find names containing exactly five characters, use five instances of the _ pattern character: */
mysql> SELECT * FROM pet WHERE name LIKE '_____';
+-------+--------+---------+------+------------+-------+
| name  | owner  | species | sex  | birth      | death |
+-------+--------+---------+------+------------+-------+
| Claws | Gwen   | cat     | m    | 1994-03-17 | NULL  |
| Buffy | Harold | dog     | f    | 1989-05-13 | NULL  |
+-------+--------+---------+------+------------+-------+

/* For more information about the syntax for regular expressions, see Regular Expressions.
   https://dev.mysql.com/doc/refman/8.0/en/regexp.html
*/
mysql> SELECT COUNT(*) FROM pet;
+----------+
| COUNT(*) |
+----------+
|        9 |
+----------+

mysql> SELECT owner, COUNT(*) FROM pet GROUP BY owner;
+--------+----------+
| owner  | COUNT(*) |
+--------+----------+
| Benny  |        2 |
| Diane  |        2 |
| Gwen   |        3 |
| Harold |        2 |
+--------+----------+

mysql> SELECT species, COUNT(*) FROM pet GROUP BY species;
+---------+----------+
| species | COUNT(*) |
+---------+----------+
| bird    |        2 |
| cat     |        2 |
| dog     |        3 |
| hamster |        1 |
| snake   |        1 |
+---------+----------+

Please follow the official examples: Retrieving Information from a Table .

Show note: “0000-00-00” dates

Some old sample datasets use 0000-00-00 as a “zero date”. In modern configurations, MySQL may reject it depending on SQL mode (e.g., stricter date validation). Use real dates or NULL when modeling “unknown”.

SQL Details

🧭 SQL Command Families: DDL / DML / DCL / TCL

SQL statements are often grouped by what they control: structure (DDL), data (DML), permissions (DCL), and transactions (TCL).

DDL — Data Definition Language
Define / change database objects

  • CREATE, DROP, ALTER
  • TRUNCATE, RENAME, COMMENT
Jump to DDL ⟶

DML — Data Manipulation Language
Query / modify rows

  • SELECT, INSERT
  • UPDATE, DELETE
Jump to DML ⟶

DCL — Data Control Language
Privileges / access control

  • GRANT, REVOKE
  • CREATE USER, DROP USER in MySQL admin
Jump to DCL ⟶

TCL — Transaction Control Language
Commit / rollback changes

  • COMMIT, ROLLBACK, SAVEPOINT
  • START TRANSACTION, SET TRANSACTION
Jump to TCL ⟶
Refer to
🧱 Data Definition Language (DDL) in MySQL
What DDL does

DDL defines or changes database objects (databases, tables, indexes, constraints). Common DDL commands: CREATE, ALTER, DROP, TRUNCATE, RENAME.

  • Note (MySQL 8.x): CHECK constraints are enforced in modern MySQL (older versions parsed but did not enforce them).
DDL mental model (quick visualization)
CREATE DB / TABLE / INDEX ALTER Columns / Constraints TRUNCATE Delete all rows DROP Remove object

DDL changes schema objects (and often triggers implicit commits). Use with care on production databases.

Comments

MySQL supports three common comment styles:

  • # to end of line
  • -- (double-dash must be followed by whitespace) to end of line
  • /* ... */ C-style block comments (can span multiple lines)
mysql> SELECT 1+1;     # This comment continues to the end of line
mysql> SELECT 1+1;     -- This comment continues to the end of line
mysql> SELECT 1 /* this is an in-line comment */ + 1;
mysql> SELECT 1+
/*
this is a
multiple-line comment
*/
1;
    
CREATE and DROP for databases

In MySQL, CREATE SCHEMA is a synonym for CREATE DATABASE. Use IF NOT EXISTS / IF EXISTS to make scripts repeatable.

/*
CREATE DATABASE databasename;

CREATE SCHEMA is a synonym for CREATE DATABASE
*/

CREATE DATABASE testDB;
CREATE SCHEMA StudentsInfo;

/* Re-runnable variants */
CREATE DATABASE IF NOT EXISTS testDB;
DROP DATABASE IF EXISTS testDB;
    
/*
DROP DATABASE databasename;
*/

DROP DATABASE testDB;
DROP SCHEMA StudentsInfo;
    
CREATE and DROP for tables
/*
CREATE TABLE table_name (
    column1 datatype,
    column2 datatype,
    column3 datatype,
    ....
);

CREATE TABLE new_table_name AS
  SELECT column1, column2, ...
  FROM existing_table_name
  WHERE ...;
*/

USE StudentsInfo;

CREATE TABLE Students (
  StudentID int,
  StudentName varchar(255),
  ParentName varchar(255),
  Address varchar(255),
  PostalCode int,
  City varchar(255)
);

CREATE TABLE TestTable AS
SELECT customername, contactname
FROM customers;
    
/*
DROP TABLE table_name;
*/

DROP TABLE Shippers;

/*
The TRUNCATE TABLE statement deletes all rows inside a table, but not the table itself.
(Usually faster than DELETE without WHERE; it also resets auto-increment counters in many cases.)
*/
TRUNCATE TABLE table_name;
    
Common gotcha

CREATE TABLE ... AS SELECT ... copies data and column definitions, but does not automatically copy indexes/keys/foreign keys from the source table.

ALTER TABLE

Use ALTER TABLE to add / modify / drop columns and constraints.

/* Add a column */
ALTER TABLE Students
ADD DateOfBirth date;

/* Drop a column */
ALTER TABLE Customers
DROP COLUMN Email;

/* Modify a column type */
ALTER TABLE Persons
MODIFY COLUMN DateOfBirth year;
    
MySQL constraints

Constraints can be specified when creating a table (CREATE TABLE) or later (ALTER TABLE). Common constraints:

  • NOT NULL — disallow NULL values
  • UNIQUE — all values must be distinct
  • PRIMARY KEY — unique + not null identifier (one per table, may be composite)
  • FOREIGN KEY — enforce references across tables
  • CHECK — enforce boolean condition on inserted/updated values (enforced in modern MySQL 8.x)
  • DEFAULT — default value if none provided
  • INDEX — speed up lookups and ordering
/*
General form:
CREATE TABLE table_name (
    column1 datatype constraint,
    column2 datatype constraint,
    column3 datatype constraint,
    ....
);
*/
    
NOT NULL examples
CREATE TABLE Persons (
  ID int NOT NULL,
  LastName varchar(255) NOT NULL,
  FirstName varchar(255) NOT NULL,
  Age int
);

ALTER TABLE Persons
MODIFY Age int NOT NULL;
      
UNIQUE examples
/*
UNIQUE ensures column values are distinct.
A PRIMARY KEY is also UNIQUE automatically.
You can have multiple UNIQUE constraints per table, but only one PRIMARY KEY.
*/

CREATE TABLE Persons (
  ID int NOT NULL,
  LastName varchar(255) NOT NULL,
  FirstName varchar(255),
  Age int,
  UNIQUE (ID)
);

ALTER TABLE Persons
ADD UNIQUE (ID);

/* Named UNIQUE constraint; can also be multi-column */
CREATE TABLE Persons (
  ID int NOT NULL,
  LastName varchar(255) NOT NULL,
  FirstName varchar(255),
  Age int,
  CONSTRAINT UC_Person UNIQUE (ID,LastName)
);

ALTER TABLE Persons
ADD CONSTRAINT UC_Person UNIQUE (ID,LastName);

/* Drop a UNIQUE constraint (implemented as an index in MySQL) */
ALTER TABLE Persons
DROP INDEX UC_Person;
      
PRIMARY KEY examples
/*
PRIMARY KEY uniquely identifies each record.
Must be UNIQUE and NOT NULL.
Can be single-column or composite.
*/

CREATE TABLE Persons (
  ID int NOT NULL,
  LastName varchar(255) NOT NULL,
  FirstName varchar(255),
  Age int,
  PRIMARY KEY (ID)
);

ALTER TABLE Persons
ADD PRIMARY KEY (ID);

/* Named composite primary key */
CREATE TABLE Persons (
  ID int NOT NULL,
  LastName varchar(255) NOT NULL,
  FirstName varchar(255),
  Age int,
  CONSTRAINT PK_Person PRIMARY KEY (ID,LastName)
);

ALTER TABLE Persons
ADD CONSTRAINT PK_Person PRIMARY KEY (ID,LastName);

/* Drop primary key */
ALTER TABLE Persons
DROP PRIMARY KEY;
      
FOREIGN KEY examples

Your original example referenced Persons(PersonID), but the table above defines Persons(ID). Below is the corrected version (same idea, correct referenced column).

/*
FOREIGN KEY prevents actions that would destroy links between tables.
Child table contains the foreign key; parent table contains the referenced key.
*/

CREATE TABLE Orders (
  OrderID int NOT NULL,
  OrderNumber int NOT NULL,
  PersonID int,
  PRIMARY KEY (OrderID),
  FOREIGN KEY (PersonID) REFERENCES Persons(ID)
);

ALTER TABLE Orders
ADD FOREIGN KEY (PersonID) REFERENCES Persons(ID);

/* Named foreign key constraint */
CREATE TABLE Orders (
  OrderID int NOT NULL,
  OrderNumber int NOT NULL,
  PersonID int,
  PRIMARY KEY (OrderID),
  CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID)
    REFERENCES Persons(ID)
);

ALTER TABLE Orders
ADD CONSTRAINT FK_PersonOrder
FOREIGN KEY (PersonID) REFERENCES Persons(ID);

/* Drop foreign key constraint */
ALTER TABLE Orders
DROP FOREIGN KEY FK_PersonOrder;
      
CHECK examples
/*
CHECK limits the value range placed in a column (or row).
*/

CREATE TABLE Persons (
  ID int NOT NULL,
  LastName varchar(255) NOT NULL,
  FirstName varchar(255),
  Age int,
  CHECK (Age>=18)
);

ALTER TABLE Persons
ADD CHECK (Age>=18);

/* Named CHECK constraint; can include multiple predicates */
CREATE TABLE Persons (
  ID int NOT NULL,
  LastName varchar(255) NOT NULL,
  FirstName varchar(255),
  Age int,
  City varchar(255),
  CONSTRAINT CHK_Person CHECK (Age>=18 AND City='Sandnes')
);

ALTER TABLE Persons
ADD CONSTRAINT CHK_PersonAge CHECK (Age>=18 AND City='Sandnes');

/* Drop CHECK constraint */
ALTER TABLE Persons
DROP CHECK CHK_PersonAge;
      
DEFAULT examples
/*
DEFAULT sets a default value for a column if no value is specified.
*/

CREATE TABLE Persons (
  ID int NOT NULL,
  LastName varchar(255) NOT NULL,
  FirstName varchar(255),
  Age int,
  City varchar(255) DEFAULT 'Sandnes'
);

ALTER TABLE Persons
ALTER City SET DEFAULT 'Sandnes';

/* You can also use functions like CURRENT_DATE() */
CREATE TABLE Orders (
  ID int NOT NULL,
  OrderNumber int NOT NULL,
  OrderDate date DEFAULT CURRENT_DATE()
);

/* Drop a DEFAULT */
ALTER TABLE Persons
ALTER City DROP DEFAULT;
      
Indexes

A database index improves query speed (especially for WHERE, JOIN, and ORDER BY). Indexes are often implemented with B-trees (general-purpose) or hash structures (engine-dependent).

How to read MySQL’s key column (e.g., from DESC)
  • PRIMARY: primary key
  • UNIQUE: unique index/constraint
  • MUL: non-unique index (multiple rows can share a value)

We can create indexes when creating a table or on an existing table:

/*
CREATE INDEX creates an index (duplicates allowed).
CREATE UNIQUE INDEX creates a unique index (duplicates disallowed).
DROP an index using ALTER TABLE ... DROP INDEX ...
*/

CREATE INDEX index_name
ON table_name (column1, column2, ...);

CREATE UNIQUE INDEX index_name
ON table_name (column1, column2, ...);

CREATE INDEX idx_lastname
ON Persons (LastName);

CREATE INDEX idx_pname
ON Persons (LastName, FirstName);

ALTER TABLE table_name
DROP INDEX index_name;
    

Example: create a new table CUSTOMERS and add an index on one column during creation:

CREATE TABLE CUSTOMERS (
  ID INT NOT NULL,
  NAME VARCHAR (20) NOT NULL,
  AGE INT NOT NULL,
  ADDRESS CHAR (25),
  SALARY DECIMAL (18, 2),
  INDEX(ID)
);

DESC CUSTOMERS;
    

The DESC output will show a MUL key on ID (indexed but not unique).

To create an index on an existing table:

  • With CREATE INDEX
  • With ALTER TABLE ... ADD INDEX
CREATE INDEX NAME_INDEX ON CUSTOMERS (Name);
DESC CUSTOMERS;

ALTER TABLE CUSTOMERS ADD INDEX AGE_INDEX (AGE);
DESC CUSTOMERS;
    
AUTO_INCREMENT

AUTO_INCREMENT generates a unique number automatically for new rows (commonly used for a primary key).

/*
AUTO_INCREMENT generates IDs automatically.
Default start is 1 and increments by 1 (unless configured otherwise).
*/

CREATE TABLE Persons (
  Personid int NOT NULL AUTO_INCREMENT,
  LastName varchar(255) NOT NULL,
  FirstName varchar(255),
  Age int,
  PRIMARY KEY (Personid)
);

/* Set the next AUTO_INCREMENT value */
ALTER TABLE Persons AUTO_INCREMENT=100;

/* Insert without specifying Personid */
INSERT INTO Persons (FirstName,LastName)
VALUES ('Lars','Monsen');
    
Practical tip

If you TRUNCATE a table, many MySQL setups reset the auto-increment counter. If you need stable IDs across reloads, consider controlling IDs explicitly.

Data Manipulation (DML) Commands

Data Manipulation Language (DML) is the part of SQL used to read and modify the data stored in tables. In this module we focus on: USE, INSERT, UPDATE, DELETE, SELECT.

Read
SELECT
Query rows and columns; can filter, sort, group, and join.
Write
INSERT / UPDATE / DELETE
Add, modify, or remove rows (often inside transactions).
Typical SELECT pipeline
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

The Typical SELECT pipeline shows the logical order in which a SQL query is processed by the database engine. Tables are first chosen in FROM, rows are filtered by WHERE, then grouped using GROUP BY. Next, HAVING filters aggregated groups, SELECT determines the output columns, ORDER BY sorts the results, and LIMIT restricts how many rows are returned. This explains why aggregate functions work in HAVING but not in WHERE.

Sample table used in examples

We will use the following sample data (table name: Infostudents).

StudentID StudentName ParentName Address City PostalCode Country Fees
01HaznitizEmizDellys RoadAfir35110Algeria42145
02ShubhamNarayanMG RoadBangalore560001India45672
03SalomaoValentimMayo RoadRio Claro27460Brazil65432
04VishalRameshQueens QuayToronto416Canada23455
05Park JiminKim Tai HyungGangnam StreetSeoul135081South Korea22353
-- Use the following commands to create this sample table.

CREATE DATABASE StudentsInfo;
USE StudentsInfo;

CREATE TABLE Infostudents (
    StudentID INT PRIMARY KEY,
    StudentName VARCHAR(50),
    ParentName VARCHAR(50),
    Address VARCHAR(100),
    City VARCHAR(50),
    PostalCode VARCHAR(20),
    Country VARCHAR(50),
    Fees INT
);

INSERT INTO Infostudents
(StudentID, StudentName, ParentName, Address, City, PostalCode, Country, Fees) VALUES
(01, 'Haznitiz', 'Emiz', 'Dellys Road', 'Afir', '35110', 'Algeria', 42145),
(02, 'Shubham', 'Narayan', 'MG Road', 'Bangalore', '560001', 'India', 45672),
(03, 'Salomao', 'Valentim', 'Mayo Road', 'Rio Claro', '27460', 'Brazil', 65432),
(04, 'Vishal', 'Ramesh', 'Queens Quay', 'Toronto', '416', 'Canada', 23455),
(05, 'Park Jimin', 'Kim Tai Hyung', 'Gangnam Street', 'Seoul', '135081', 'South Korea', 22353);

USE

DML Pick the database

USE selects the default database (schema) for subsequent queries.

USE StudentsInfo;

INSERT

Tip
Prefer explicit column lists
It prevents mistakes if the table schema changes later.
Modern SQL
Insert multiple rows
One statement can insert many rows efficiently.
-- to insert new records in a table

INSERT INTO Infostudents(StudentID, StudentName, ParentName, Address, City, PostalCode, Country)
VALUES ('06', 'Sanjana','Jagannath', 'Banjara Hills', 'Hyderabad', '500046', 'India');

-- Note: this second INSERT must provide values for *all* columns in the table.
-- Your original line is missing (Country, Fees). Keep it only if your table truly has fewer columns.

INSERT INTO Infostudents
VALUES ('07', 'Shivantini','Praveen', 'Camel Street', 'Kolkata', '700096', 'India', 21000);

UPDATE

-- to modify the existing records in a table

UPDATE Infostudents
SET StudentName = 'Alfred', City = 'Frankfurt'
WHERE StudentID = 1;

DELETE

-- to delete existing records in a table

DELETE FROM Infostudents
WHERE StudentName = 'Salomao';
Tip: preview before deleting
SELECT * FROM Infostudents
WHERE StudentName = 'Salomao';
  

SELECT

🔎
SELECT essentials
  • Projection: choose columns (SELECT StudentName, City)
  • Filtering: choose rows (WHERE ...)
  • Sorting: order results (ORDER BY ...)
  • Grouping: aggregate results (GROUP BY ... HAVING ...)
-- to select data from a database and the data returned is stored in a result table (result-set)

SELECT StudentName, City FROM Infostudents;
SELECT * FROM Infostudents;

-- to return only distinct or different values (FIXED: table name)
SELECT DISTINCT Country FROM Infostudents;

-- to sort the desired results in ascending or descending order

SELECT * FROM Infostudents
ORDER BY Country;

SELECT * FROM Infostudents
ORDER BY Country DESC;

SELECT * FROM Infostudents
ORDER BY Country, StudentName;

SELECT * FROM Infostudents
ORDER BY Country ASC, StudentName DESC;

-- to group the result-set by one or more columns

SELECT COUNT(StudentID), Country
FROM Infostudents
GROUP BY Country
ORDER BY COUNT(StudentID) DESC;

-- HAVING (FIXED: SUM, not COUNT and not SUN)

SELECT COUNT(StudentID), City
FROM Infostudents
GROUP BY City
HAVING SUM(Fees) > 23000;

-- LOGICAL OPERATORS

SELECT * FROM Infostudents
WHERE Country='Brazil' AND City='Rio Claro';

SELECT * FROM Infostudents
WHERE City='Toronto' OR City='Seoul';

SELECT * FROM Infostudents
WHERE NOT Country='India';

-- Combine operators (FIXED: City='Canada' is not a city in the data; use Bangalore/Kolkata/Hyderabad/etc.)
SELECT * FROM Infostudents
WHERE Country='India' AND (City='Bangalore' OR City='Kolkata');
Difference Between HAVING and WHERE in SQL
FeatureWHEREHAVING
UsageFilters rows before groupingFilters groups after GROUP BY
Aggregate functions?❌ No✅ Yes
OrderBefore GROUP BYAfter GROUP BY
Example 1: Using WHERE (Before Grouping)
SELECT City, COUNT(StudentID) AS StudentCount, SUM(Fees) AS TotalFees
FROM Infostudents
WHERE Country = 'India'
GROUP BY City;
  
Example 2: Using HAVING (After Grouping)
SELECT City, COUNT(StudentID) AS StudentCount, SUM(Fees) AS TotalFees
FROM Infostudents
GROUP BY City
HAVING SUM(Fees) > 23000;
  

Aggregate functions

-- AGGREGATE FUNCTIONS:

-- MIN()
SELECT MIN(StudentID) AS SmallestID
FROM Infostudents;

-- MAX()
SELECT MAX(Fees) AS MaximumFees
FROM Infostudents;

-- COUNT()
SELECT COUNT(StudentID)
FROM Infostudents;

-- AVG()
SELECT AVG(Fees)
FROM Infostudents;

-- SUM()
SELECT SUM(Fees)
FROM Infostudents;

Special operators

-- BETWEEN
SELECT * FROM Infostudents
WHERE Fees BETWEEN 20000 AND 40000;

-- IS NULL / IS NOT NULL
SELECT StudentName, ParentName, Address FROM Infostudents
WHERE Address IS NULL;

SELECT StudentName, ParentName, Address FROM Infostudents
WHERE Address IS NOT NULL;

-- LIKE: pattern matching
SELECT * FROM Infostudents
WHERE StudentName LIKE 'S%';
LIKE patternDescription
LIKE 'z%'Starts with “z”
LIKE '%z'Ends with “z”
LIKE '%and%'Contains “and”
LIKE '_s%'Second character is “s”
LIKE 'd_%_%'Starts with “d” and length ≥ 3
LIKE 'j%l'Starts with “j” and ends with “l”
-- IN
SELECT * FROM Infostudents
WHERE Country IN ('Algeria', 'India', 'Brazil');

-- EXISTS (FIXED: column names; use Fees, not Price; StudentID, not StudentId)
SELECT StudentName
FROM Infostudents s
WHERE EXISTS (
  SELECT 1
  FROM Infostudents t
  WHERE t.StudentID = 5 AND t.Fees < 25000
);

-- ALL: true if condition holds for all values in subquery
SELECT StudentName
FROM Infostudents
WHERE StudentID != ALL (SELECT StudentID FROM Infostudents WHERE Fees > 30000);

-- ANY: true if condition holds for any value in subquery (FIXED: StudentID spelling)
SELECT StudentName
FROM Infostudents
WHERE StudentID = ANY (SELECT StudentID FROM Infostudents WHERE Fees BETWEEN 22000 AND 23000);
More examples on ALL and ANY Setup: Employees table
CREATE TABLE Employees (
    id INT,
    name VARCHAR(50),
    salary DECIMAL(10,2),
    department VARCHAR(50)
);

INSERT INTO Employees VALUES
(1, 'Alice',   5000, 'HR'),
(2, 'Bob',     7000, 'IT'),
(3, 'Charlie', 6000, 'IT'),
(4, 'David',   8000, 'Finance'),
(5, 'Eva',     5500, 'HR');
  
Example 1: Salary greater than all salaries in HR
SELECT name, salary
FROM Employees
WHERE salary > ALL (
    SELECT salary
    FROM Employees
    WHERE department = 'HR'
);

/*
HR salaries = [5000, 5500]
Condition: salary > every value → salary > 5500
Result: Bob, Charlie, David
(> ALL ≈ > MAX)
*/
  
Example 2: Salary less than all salaries in IT
SELECT name, salary
FROM Employees
WHERE salary < ALL (
    SELECT salary
    FROM Employees
    WHERE department = 'IT'
);

/*
IT salaries = [7000, 6000]
Condition: salary < every value → salary < 6000
Result: Alice, Eva
(< ALL ≈ < MIN)
*/
  
Example 3: Salary greater than any salary in HR
SELECT name, salary
FROM Employees
WHERE salary > ANY (
    SELECT salary
    FROM Employees
    WHERE department = 'HR'
);

/*
Condition: salary > at least one value
Result: Bob, Charlie, David, Eva
(> ANY ≈ > MIN)
*/
  
Example 4: Salary less than any salary in IT
SELECT name, salary
FROM Employees
WHERE salary < ANY (
    SELECT salary
    FROM Employees
    WHERE department = 'IT'
);

/*
Condition: salary < at least one value
Result: Alice, Eva, Charlie
(< ANY ≈ < MAX)
*/
  
Example 5: = ALL (true only if all values are identical)
SELECT name
FROM Employees
WHERE department = ALL (
    SELECT department
    FROM Employees
    WHERE name IN ('Alice', 'Eva')
);

/*
Subquery result = [HR, HR]
Condition holds → Alice, Eva
*/
  
Example 6: = ANY is equivalent to IN
SELECT name
FROM Employees
WHERE department = ANY (
    SELECT department
    FROM Employees
    WHERE department IN ('HR', 'IT')
);

-- Equivalent to:
SELECT name
FROM Employees
WHERE department IN ('HR', 'IT');
  

JOINS

JOINS combine rows from multiple tables based on a related column (typically keys).

  • INNER JOIN: only matching rows in both tables
  • LEFT JOIN: all rows from left + matching rows from right
  • RIGHT JOIN: all rows from right + matching rows from left
  • FULL OUTER JOIN: all rows from both sides (⚠️ not directly supported in MySQL; use UNION pattern)
Joins
Types of Joins (image credit: edureka.co)
-- Generic JOIN templates

SELECT column_name(s)
FROM table1
INNER JOIN table2 ON table1.column_name = table2.column_name;

SELECT column_name(s)
FROM table1
LEFT JOIN table2 ON table1.column_name = table2.column_name;

SELECT column_name(s)
FROM table1
RIGHT JOIN table2 ON table1.column_name = table2.column_name;

-- NOTE: MySQL does not support FULL OUTER JOIN directly.
-- Use the UNION workaround shown in the practice block below.
Practice on Join Students Table
-- Create the Departments Table

CREATE TABLE Departments (
    DeptID INT PRIMARY KEY,
    DeptName VARCHAR(50)
);

INSERT INTO Departments (DeptID, DeptName) VALUES
(100, 'HR'),
(200, 'Engineering'),
(500, 'Sales');


-- Create the Employees Table

CREATE TABLE Employees (
    EmployeeID INT PRIMARY KEY,
    EmployeeName VARCHAR(50),
    DeptID INT,
); -- FOREIGN KEY (DeptID) REFERENCES Departments(DeptID)

INSERT INTO Employees (EmployeeID, EmployeeName, DeptID) VALUES
(1, 'Alice', 100),
(2, 'Bob', 200),
(3, 'Carol', 100),
(4, 'Dave', 300);


SELECT * FROM Departments;
+--------+-------------+
| DeptID | DeptName    |
+--------+-------------+
|    100 | HR          |
|    200 | Engineering |
|    500 | Sales       |
+--------+-------------+

SELECT * FROM Employees;
+------------+--------------+--------+
| EmployeeID | EmployeeName | DeptID |
+------------+--------------+--------+
|          1 | Alice        |    100 |
|          2 | Bob          |    200 |
|          3 | Carol        |    100 |
|          4 | Dave         |    300 |
+------------+--------------+--------+
-- INNER JOIN

SELECT *
FROM Employees
INNER JOIN Departments ON Employees.DeptID = Departments.DeptID;

+------------+--------------+--------+--------+-------------+
| EmployeeID | EmployeeName | DeptID | DeptID | DeptName    |
+------------+--------------+--------+--------+-------------+
|          1 | Alice        |    100 |    100 | HR          |
|          2 | Bob          |    200 |    200 | Engineering |
|          3 | Carol        |    100 |    100 | HR          |
+------------+--------------+--------+--------+-------------+

SELECT Employees.EmployeeID, Departments.DeptName
FROM Employees
INNER JOIN Departments ON Employees.DeptID = Departments.DeptID;

+------------+-------------+
| EmployeeID | DeptName    |
+------------+-------------+
|          1 | HR          |
|          2 | Engineering |
|          3 | HR          |
+------------+-------------+

-- LEFT JOIN

SELECT *
FROM Employees
LEFT JOIN Departments ON Employees.DeptID = Departments.DeptID;

+------------+--------------+--------+--------+-------------+
| EmployeeID | EmployeeName | DeptID | DeptID | DeptName    |
+------------+--------------+--------+--------+-------------+
|          1 | Alice        |    100 |    100 | HR          |
|          2 | Bob          |    200 |    200 | Engineering |
|          3 | Carol        |    100 |    100 | HR          |
|          4 | Dave         |    300 |   NULL | NULL        |
+------------+--------------+--------+--------+-------------+

-- RIGHT JOIN

SELECT *
FROM Employees
RIGHT JOIN Departments ON Employees.DeptID = Departments.DeptID;

+------------+--------------+--------+--------+-------------+
| EmployeeID | EmployeeName | DeptID | DeptID | DeptName    |
+------------+--------------+--------+--------+-------------+
|          3 | Carol        |    100 |    100 | HR          |
|          1 | Alice        |    100 |    100 | HR          |
|          2 | Bob          |    200 |    200 | Engineering |
|       NULL | NULL         |   NULL |    500 | Sales       |
+------------+--------------+--------+--------+-------------+

-- FULL JOIN

SELECT *
FROM Employees
LEFT JOIN Departments ON Employees.DeptID = Departments.DeptID
UNION ALL
SELECT *
FROM Employees
RIGHT JOIN Departments ON Employees.DeptID = Departments.DeptID
WHERE Employees.DeptID IS NULL;

+------------+--------------+--------+--------+-------------+
| EmployeeID | EmployeeName | DeptID | DeptID | DeptName    |
+------------+--------------+--------+--------+-------------+
|          1 | Alice        |    100 |    100 | HR          |
|          2 | Bob          |    200 |    200 | Engineering |
|          3 | Carol        |    100 |    100 | HR          |
|          4 | Dave         |    300 |   NULL | NULL        |
|       NULL | NULL         |   NULL |    500 | Sales       |
+------------+--------------+--------+--------+-------------+

Data Control Language (DCL)

Data Control Language (DCL) is a subset of SQL used to control access to database objects such as tables, views, and stored procedures. DCL focuses on security, authorization, and permission management.

In MySQL, DCL is primarily implemented through the GRANT and REVOKE commands, which assign or remove privileges for users and roles.

Privileges can be applied at different levels: *.* (global), database.*, or database.table.

Create and Drop Users

In MySQL, users are identified by both a username and a host. This allows fine-grained control over where users can connect from.

CREATE USER 'username'@'host' IDENTIFIED BY 'password';
  
  • username: Account name
  • host: Allowed connection source (localhost, %, IP)
  • password: User authentication secret
-- Local-only user
CREATE USER 'john'@'localhost' IDENTIFIED BY 'secure_password';
  
-- User allowed from any host
CREATE USER 'john'@'%' IDENTIFIED BY 'secure_password';
  

MySQL stores user account information internally. Administrators can inspect user metadata using:

SELECT User, Host, authentication_string
FROM mysql.user;
  
-- Remove a user completely
DROP USER 'john'@'localhost';
  

GRANT: Assigning Privileges

The GRANT command assigns permissions to users on specific database objects.

GRANT privilege_name
ON object_name
TO 'username'@'host';
  
-- Grant SELECT on all tables in a database
GRANT SELECT ON employees.* TO 'john'@'localhost';
  
-- Grant multiple privileges on a table
GRANT SELECT, INSERT, UPDATE
ON employees
TO 'john'@'localhost';
  
-- Grant full privileges on a database
GRANT ALL PRIVILEGES ON employees.* TO 'john'@'localhost';
  
-- Grant full privileges on all databases (admin-level)
GRANT ALL PRIVILEGES ON *.* TO 'john'@'localhost';
  
-- Allow user to re-grant privileges to others
GRANT SELECT
ON employees.*
TO 'john'@'localhost'
WITH GRANT OPTION;
  

Inspecting Privileges

To see what privileges a user currently has:

SHOW GRANTS FOR 'john'@'localhost';
  

REVOKE: Removing Privileges

The REVOKE command removes previously granted privileges.

REVOKE privilege_name
ON object_name
FROM 'username'@'host';
  
-- Remove INSERT privilege
REVOKE INSERT ON employees.* FROM 'john'@'localhost';
  
-- Remove multiple privileges
REVOKE SELECT, INSERT, UPDATE
ON employees
FROM 'john'@'localhost';
  
-- Remove all privileges on a database
REVOKE ALL PRIVILEGES
ON employees.*
FROM 'john'@'localhost';
  
Important Notes
  • Only administrative users (e.g., root) can manage DCL.
  • Grant the minimum privileges necessary (principle of least privilege).
  • Be extremely cautious with GRANT ALL ON *.*.
  • In MySQL 8+, privileges take effect immediately (no FLUSH needed).

Transaction Control Language (TCL)

Transaction Control Language (TCL) commands are used to manage transactions in a database system. A transaction is a sequence of database operations that are treated as a single logical unit of work.

TCL ensures that database changes are applied in a safe, consistent, and recoverable way: either all changes succeed or none of them take effect.

Finalize
COMMIT
Permanently saves all changes made during a transaction.
Undo
ROLLBACK
Reverts all changes made since the transaction started.
Checkpoint
SAVEPOINT
Marks a point inside a transaction for partial rollback.
Policy
SET TRANSACTION
Configures transaction properties such as isolation level.

COMMIT

The COMMIT command permanently saves all changes made during the current transaction.

COMMIT;
  
START TRANSACTION;

INSERT INTO employees (name, department, salary)
VALUES ('John Doe', 'IT', 60000);

UPDATE employees
SET salary = salary + 5000
WHERE name = 'Jane Smith';

COMMIT;
  
  • START TRANSACTION begins a new transaction.
  • All changes remain temporary until COMMIT.
  • After COMMIT, changes become permanent and visible to others.

ROLLBACK

The ROLLBACK command undoes all changes made during the current transaction.

ROLLBACK;
  
START TRANSACTION;

INSERT INTO employees (name, department, salary)
VALUES ('John Doe', 'IT', 60000);

UPDATE employees
SET salary = salary + 5000
WHERE name = 'Jane Smith';

-- An error occurs
ROLLBACK;
  

After ROLLBACK, the database state is exactly the same as before the transaction began.

SAVEPOINT

A SAVEPOINT creates a named checkpoint inside a transaction. You can roll back to a savepoint without canceling the entire transaction.

SAVEPOINT savepoint_name;
  
START TRANSACTION;

INSERT INTO employees (name, department, salary)
VALUES ('John Doe', 'IT', 60000);
SAVEPOINT sp1;

INSERT INTO employees (name, department, salary)
VALUES ('Jane Smith', 'HR', 70000);
SAVEPOINT sp2;

UPDATE employees
SET salary = 75000
WHERE name = 'Jane Smith';

ROLLBACK TO sp2;
COMMIT;
  
  • sp1 and sp2 mark intermediate states.
  • ROLLBACK TO sp2 undoes only the last update.
  • Earlier operations remain intact.

RELEASE SAVEPOINT

The RELEASE SAVEPOINT command removes a savepoint. After release, you can no longer roll back to that savepoint.

RELEASE SAVEPOINT savepoint_name;
  
START TRANSACTION;

UPDATE accounts SET balance = balance - 50 WHERE id = 1;
SAVEPOINT sp1;

UPDATE accounts SET balance = balance - 50 WHERE id = 1;
RELEASE SAVEPOINT sp1;

COMMIT;
  

SET TRANSACTION

SET TRANSACTION configures transaction behavior, including read/write mode and isolation level.

SET TRANSACTION
[READ ONLY | READ WRITE]
ISOLATION LEVEL
{READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE};
  
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;

SELECT * FROM employees
WHERE department = 'IT';

COMMIT;
  
ACID Properties of Transactions
  • Atomicity: All operations succeed or none do.
  • Consistency: Database moves between valid states.
  • Isolation: Concurrent transactions do not interfere.
  • Durability: Committed changes survive failures.

Transaction Isolation Levels

  • READ UNCOMMITTED — Allows dirty reads (rarely used).
  • READ COMMITTED — Prevents dirty reads.
  • REPEATABLE READ — Default in MySQL; prevents non-repeatable reads.
  • SERIALIZABLE — Full isolation; strongest consistency guarantees.
Important Notes
  • Use transactions for multi-step updates.
  • MySQL transactions require InnoDB tables.
  • DDL statements (e.g., CREATE TABLE) implicitly commit.
  • Choose isolation levels carefully for performance vs consistency.
🧩 Practice 1

Most practical SQL work can be expressed using a small set of patterns:

  • Filter with WHERE (predicates, NULL handling).
  • Project columns with SELECT (expressions, aliases).
  • Aggregate with GROUP BY + HAVING.
  • Sort & limit with ORDER BY + LIMIT.
  • Join tables on keys (INNER/LEFT/RIGHT joins).

Conceptually, you can think of a SELECT query as a pipeline: FROMWHEREGROUP BYHAVINGSELECTORDER BYLIMIT.

We will reuse these tables in exercises:

Student(sid, name, major)
  • sid PK
  • name
  • major
Enroll(sid, cid, grade)
  • sid FK
  • cid FK
  • grade
Course(cid, title, credits)
  • cid PK
  • title
  • credits
Exercise: Basic SELECT
Easy

Write a query to list all rows and columns from Student.

Show hint

Use SELECT *.

Show answer
SELECT * FROM Student;
Exercise: Projection & alias
Easy

List student names and majors, and rename the columns as student_name and program.

Show hint

Use AS for aliases.

Show answer
SELECT name AS student_name, major AS program
FROM Student;
Exercise: Filtering
Easy

List all CS majors (major = 'CS').

Show hint

Add a WHERE clause.

Show answer
SELECT * FROM Student
WHERE major = 'CS';
Exercise: IN and DISTINCT
Medium

Return the distinct majors that are either 'CS', 'DS', or 'SE'.

Show hint

Use DISTINCT and IN (...).

Show answer
SELECT DISTINCT major
FROM Student
WHERE major IN ('CS','DS','SE');
Exercise: Pattern matching
Medium

Find students whose name starts with 'A'.

Show hint

Use LIKE 'A%'.

Show answer
SELECT * FROM Student
WHERE name LIKE 'A%';
Exercise 10: ORDER BY + LIMIT
Medium

List the 5 courses with the largest number of credits (ties allowed).

Show hint

Sort descending, then limit.

Show answer
SELECT * FROM Course
ORDER BY credits DESC
LIMIT 5;
Exercise 11: INNER JOIN
Medium

List student name, course title, and grade for all enrollments.

Show hint

Join Student↔Enroll and Enroll↔Course on sid/cid.

Show answer
SELECT s.name, c.title, e.grade
FROM Enroll e
JOIN Student s ON e.sid = s.sid
JOIN Course  c ON e.cid = c.cid;
Exercise: LEFT JOIN for missing matches
Medium

List all students and their enrollments (include students with no enrollments).

Show hint

Start from Student and LEFT JOIN Enroll.

Show answer
SELECT s.sid, s.name, e.cid, e.grade
FROM Student s
LEFT JOIN Enroll e ON e.sid = s.sid;
Exercise: GROUP BY + HAVING
Hard

Find course IDs with at least 10 enrolled students.

Show hint

Group enrollments by cid; filter groups using HAVING.

Show answer
SELECT cid
FROM Enroll
GROUP BY cid
HAVING COUNT(*) >= 10;
Exercise: “students with no enrollments”
Hard

Return the names of students who are not enrolled in any course.

Show hint

LEFT JOIN and filter where the joined key is NULL, or use NOT EXISTS.

Show answer
SELECT s.name
FROM Student s
LEFT JOIN Enroll e ON e.sid = s.sid
WHERE e.sid IS NULL;
Exercise: Correlated subquery
Hard

For each student, list the number of courses they are enrolled in (including 0).

Show hint

LEFT JOIN + GROUP BY is usually simplest; correlated subquery also works.

Show answer
SELECT s.sid, s.name, COUNT(e.cid) AS num_courses
FROM Student s
LEFT JOIN Enroll e ON e.sid = s.sid
GROUP BY s.sid, s.name;
Exercise: NULL semantics
Hard

Suppose some Enroll.grade values are NULL (not yet assigned). Write a query to list all enrollments that do not have a grade yet.

Show hint

Use IS NULL, not = NULL.

Show answer
SELECT * FROM Enroll
WHERE grade IS NULL;
🧩 Practice 2: Common Queries

Dataset Setup RUN FIRST

We create a small table shop(article, dealer, price) with a composite primary key (article, dealer).

CREATE TABLE shop (
  article INT UNSIGNED  DEFAULT 0    NOT NULL,
  dealer  CHAR(20)      DEFAULT ''   NOT NULL,
  price   DECIMAL(16,2) DEFAULT 0.00 NOT NULL,
  PRIMARY KEY(article, dealer)
);

INSERT INTO shop VALUES
  (1,'A',3.45),(1,'B',3.99),(2,'A',10.99),(3,'B',1.45),
  (3,'C',1.69),(3,'D',1.25),(4,'D',19.95);

SELECT * FROM shop ORDER BY article;
+---------+--------+-------+
| article | dealer | price |
+---------+--------+-------+
|       1 | A      |  3.45 |
|       1 | B      |  3.99 |
|       2 | A      | 10.99 |
|       3 | B      |  1.45 |
|       3 | C      |  1.69 |
|       3 | D      |  1.25 |
|       4 | D      | 19.95 |
+---------+--------+-------+
      
Typical “find the max” patterns
MAX() aggregate  •  ② correlated subquery  •  ③ ORDER BY ... LIMIT  •  ④ (Up-to-date) window functions

1. What is the highest item number?

Show answer
SELECT MAX(article) AS article
FROM shop;

+---------+
| article |
+---------+
|       4 |
+---------+
      

2. Find the number, dealer, and price of the most expensive article.

Show answer
-- (A) Subquery with MAX(price)
SELECT article, dealer, price
FROM shop
WHERE price = (SELECT MAX(price) FROM shop);

+---------+--------+-------+
| article | dealer | price |
+---------+--------+-------+
|       4 | D      | 19.95 |
+---------+--------+-------+
      
-- (B) Anti-join (find rows that have no strictly higher price)
SELECT s1.article, s1.dealer, s1.price
FROM shop s1
LEFT JOIN shop s2
  ON s1.price < s2.price
WHERE s2.article IS NULL;
      
-- (C) ORDER BY + LIMIT (fast + simple)
SELECT article, dealer, price
FROM shop
ORDER BY price DESC
LIMIT 1;
      
-- (D) MySQL 8+ window function (handles ties cleanly if you want)
SELECT article, dealer, price
FROM (
  SELECT s.*,
         DENSE_RANK() OVER (ORDER BY price DESC) AS rnk
  FROM shop s
) t
WHERE rnk = 1;
      

3. Find the highest price per article.

Show answer
SELECT article, MAX(price) AS price
FROM shop
GROUP BY article
ORDER BY article;

+---------+-------+
| article | price |
+---------+-------+
|       1 |  3.99 |
|       2 | 10.99 |
|       3 |  1.69 |
|       4 | 19.95 |
+---------+-------+
      

4. For each article, find the dealer(s) with the most expensive price.

Show answer
-- (A) Correlated subquery (classic)
SELECT article, dealer, price
FROM shop s1
WHERE price = (
  SELECT MAX(s2.price)
  FROM shop s2
  WHERE s1.article = s2.article
)
ORDER BY article;

+---------+--------+-------+
| article | dealer | price |
+---------+--------+-------+
|       1 | B      |  3.99 |
|       2 | A      | 10.99 |
|       3 | C      |  1.69 |
|       4 | D      | 19.95 |
+---------+--------+-------+
      
-- (B) MySQL 8+ window function (great for ties)
SELECT article, dealer, price
FROM (
  SELECT s.*,
         DENSE_RANK() OVER (PARTITION BY article ORDER BY price DESC) AS rnk
  FROM shop s
) t
WHERE rnk = 1
ORDER BY article;
      

5. Using user-defined variables to find the articles with the highest and lowest price.

Show answer
SELECT @min_price := MIN(price),
       @max_price := MAX(price)
FROM shop;

SELECT *
FROM shop
WHERE price = @min_price OR price = @max_price;

+---------+--------+-------+
| article | dealer | price |
+---------+--------+-------+
|       3 | D      |  1.25 |
|       4 | D      | 19.95 |
+---------+--------+-------+
      
-- (Up-to-date) MySQL 8+ window function alternative
SELECT article, dealer, price
FROM (
  SELECT s.*,
         MIN(price) OVER () AS min_price,
         MAX(price) OVER () AS max_price
  FROM shop s
) t
WHERE price = min_price OR price = max_price;
      
🧩 Practice 3: DCL & TCL

Scenario PERMISSIONS + TRANSACTION

You create an orders table, grant privileges to a user, revoke one privilege, then run a transaction with a savepoint.

CREATE TABLE orders (
  order_id    INT UNSIGNED     DEFAULT 0    NOT NULL,
  customer_id INT UNSIGNED     DEFAULT 0    NOT NULL,
  amount      DECIMAL(16,2)    DEFAULT 0.00 NOT NULL,
  PRIMARY KEY(order_id)
);

INSERT INTO orders VALUES
  (1, 101, 200.50),
  (2, 102, 150.75),
  (3, 103, 300.40);

SELECT * FROM orders;
+----------+-------------+--------+
| order_id | customer_id | amount |
+----------+-------------+--------+
|        1 |         101 | 200.50 |
|        2 |         102 | 150.75 |
|        3 |         103 | 300.40 |
+----------+-------------+--------+

CREATE USER 'john'@'localhost' IDENTIFIED BY 'password';

-- Correct MySQL syntax: GRANT ... ON db.table ...
-- (If you omit db, it may fail depending on current database.)
GRANT SELECT, UPDATE ON your_db_name.orders TO 'john'@'localhost';

-- In modern MySQL, FLUSH PRIVILEGES is NOT needed after GRANT/REVOKE.
-- (It is mainly for when you manually edit privilege tables.)
-- FLUSH PRIVILEGES;

-- SHOW GRANTS FOR 'john'@'localhost';

-- Correct quoting for host is with single quotes
REVOKE UPDATE ON your_db_name.orders FROM 'john'@'localhost';

-- SHOW GRANTS FOR 'john'@'localhost';

START TRANSACTION;
UPDATE orders SET amount = amount + 10 WHERE order_id = 1;
SAVEPOINT sp1;
UPDATE orders SET amount = amount + 20 WHERE order_id = 2;
ROLLBACK TO sp1;
COMMIT;
    

1. What permissions does user 'john' have on orders after GRANT and REVOKE?

Show answer
SHOW GRANTS FOR 'john'@'localhost';

-- Result: john has SELECT on your_db_name.orders
-- UPDATE was revoked, so it is no longer present.
      

2. What will be the amounts for order_id = 1 and order_id = 2 after the transaction?

Show answer
-- order_id = 1:
-- 200.50 + 10 = 210.50 (committed)

-- order_id = 2:
-- The +20 update happened after savepoint sp1 and was rolled back.
-- So it remains 150.75.
      

3. How do you set a transaction to be READ ONLY?

Show answer
SET TRANSACTION READ ONLY;
START TRANSACTION;
-- queries...
COMMIT;
      

4. How do you release a savepoint?

Show answer
RELEASE SAVEPOINT sp1;
      

5. How do you roll back a transaction to the beginning?

Show answer
ROLLBACK;
      

Please follow the examples on Examples of Common Queries.

MySQL WorkBench

MySQL Workbench

MySQL Workbench is a unified, visual tool for database architects, developers, and DBAs. It combines data modeling, SQL development, and administration in one GUI, so you can design schemas, run queries, and manage users/servers without relying only on command-line tools. It runs on Windows, macOS, and Linux. (Oracle’s release notes show ongoing 8.0.x releases, e.g., 8.0.44 as a GA release dated 2025-10-22.)

SQL Development
Write & run queries faster
Syntax highlighting, auto-completion, and a visual result grid for exploring/exporting query results.
Data Modeling
ERD + forward/reverse engineering
Design schemas with EER/ER diagrams, generate DDL, or reverse-engineer an existing database into a model.
Administration
Users, privileges, server settings
Manage accounts/privileges and common server tasks from one place (especially helpful for beginners).
Performance
Explain plans & monitoring
Inspect query plans and monitor basic server metrics to diagnose slow queries and bottlenecks.
Where Workbench fits in a typical workflow
Model schema (ERD) → generate DDL → run DML queries → inspect plans/performance → manage users/privileges → backup/export
Key features (kept from your original list, with a few modern workflow notes)
  1. SQL Development
    • Create and execute SQL queries in an integrated editor.
    • Syntax highlighting + auto-completion to reduce mistakes.
    • Browse and edit table data visually (insert/update/delete in grid view).
  2. Data Modeling and Design
    • Visual tools for creating Entity-Relationship Diagrams (ERD / EER).
    • Forward engineering (model → database) and reverse engineering (database → model).
    • Model/database synchronization to compare and apply schema changes more safely.
  3. Database Administration
    • User, privilege, and (in many setups) role management.
    • Performance monitoring/diagnostics (useful when teaching “why is this query slow?”).
    • Common maintenance tasks (configuration views, server status dashboards, etc.).
  4. Migration Tools
    • Migration wizard to help move schemas/data from other databases into MySQL (capabilities vary by source DB and drivers).
  5. Database Design Validation
    • Basic checks for modeling/syntax issues before implementing a schema.
  6. Schema and Server Monitoring
    • Track connections, query activity, and other high-level health metrics.
  7. User and Privilege Management
    • Create/modify/delete accounts and adjust permissions through a GUI.
    • Good companion to your DCL section (GRANT/REVOKE), because students can “see” the outcome.
  8. Database Backup and Recovery
    • Export/import features for moving data or making snapshots (exact options depend on OS and installed components).
    • In practice, DBAs often pair Workbench with command-line tools (e.g., mysqldump/mysql or MySQL Shell) for automation.
Quick start (beginner-friendly)
  • Connect: Home → “MySQL Connections” → new connection → set Host/Port/User + (optional) SSL/SSH tunnel.
  • Run queries: Open SQL Editor → write SQL → execute → inspect results in the grid.
  • Modeling: File → New Model → add tables/relations → Forward Engineer to generate DDL.
  • Reverse engineer: Database → Reverse Engineer → select schema → generate EER diagram.
🎥 Further learning: MySQL Workbench

MySQL Workbench Tutorial

MySQL Workbench Tutorial

MySQL Workbench Tutorial

🎥 Further learning: MySQL Full Course

MySQL Full Course

MySQL with Python

Python is commonly used to build database-backed applications, and MySQL is one of the most widely used relational databases. To run the examples below, make sure you have a MySQL server installed (local or remote), and a Python environment ready.

Goal
Use SQL inside Python
You write SQL (SELECT, INSERT, UPDATE, …) and send it to MySQL using a Python driver.
Core idea
Connection → Cursor → Execute → Fetch/Commit
connect() makes a session; a cursor runs SQL; you fetch* results; you commit() writes.
Typical workflow in Python DB apps
connect → cursor → execute → fetch (for SELECT) / commit (for writes) → close

1) Install a MySQL Driver

📦 Recommended driver: MySQL Connector/Python

Python needs a MySQL driver to talk to the MySQL server. A common choice is MySQL Connector/Python (mysql-connector-python), maintained by Oracle/MySQL. (As of Oct 2025, the PyPI release is in the 9.x series.)

pip install mysql-connector-python
  

Or:

python -m pip install mysql-connector-python
  
Up-to-date note

If you run into compatibility issues, upgrade pip first and ensure you’re using a supported Python version. Also note that mysql-connector-python is actively updated on PyPI.

2) Test the Installation

Quick import test

To test if the installation was successful, create a Python script and run it:

import mysql.connector
print("mysql.connector imported successfully!")
  
Common error: ModuleNotFoundError: No module named 'mysql.connector'; 'mysql' is not a package
  • Make sure you installed the package into the same Python environment you run.
  • Check you don’t have a local file named mysql.py that shadows the package.

3) Create a Connection

🔌 Connect to the server

Start by creating a connection using a MySQL username and password. (In real projects, avoid hard-coding passwords—use environment variables or a secrets manager.)

MySQL user creation (SQL)
CREATE USER 'yourusername'@'localhost' IDENTIFIED BY 'yourpassword';
    
import mysql.connector

mydb = mysql.connector.connect(
  host="localhost",
  user="yourusername",
  password="yourpassword"
)

print(mydb)
  
Tip: verify connection + server version
cur = mydb.cursor()
cur.execute("SELECT VERSION();")
print(cur.fetchone())
cur.close()
    

4) Cursor Basics

🧭 What is a cursor?

A cursor executes SQL commands and lets you fetch results. Think of it as the “handle” you use to send SQL to MySQL and read returned rows.

Common cursor methods
MethodDescription
execute(query, params=None)Run one SQL statement.
executemany(query, seq_of_params)Run the same SQL statement multiple times.
fetchone()Get the next row.
fetchall()Get all remaining rows.
close()Close the cursor.
lastrowidID of the last inserted row (when available).
Buffered cursor (helps avoid “Unread result found”)

If you execute a query that returns rows, you should fetch them before running a new statement on the same cursor. A common practical option is to use a buffered cursor for small/medium result sets:

cur = mydb.cursor(buffered=True)
    

5) Create / Select a Database

🗄️ Create a database
import mysql.connector

mydb = mysql.connector.connect(
  host="localhost",
  user="yourusername",
  password="yourpassword"
)

mycursor = mydb.cursor()
mycursor.execute("CREATE DATABASE mydatabase")
  
If you encounter: 1044 (42000): Access denied for user 'yourusername'@'localhost' to database 'mydatabase'
  • Your user likely lacks CREATE privileges on databases.
  • Ask an admin / root user to grant the minimum needed privileges.

List databases:

mycursor.execute("SHOW DATABASES")
for x in mycursor:
  print(x)
  

Connect directly to a database:

mydb = mysql.connector.connect(
  host="localhost",
  user="yourusername",
  password="yourpassword",
  database="mydatabase"
)
  

6) Create Tables (with PRIMARY KEY)

🏗️ Create a table
mycursor = mydb.cursor()
mycursor.execute("CREATE TABLE customers (name VARCHAR(255), address VARCHAR(255))")
  

Show tables:

mycursor.execute("SHOW TABLES")
for x in mycursor:
  print(x)
  

When creating a table, you usually want a unique key for each row (a PRIMARY KEY). In MySQL, AUTO_INCREMENT is commonly used for an integer primary key.

mycursor.execute("""
CREATE TABLE customers (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255),
  address VARCHAR(255)
)
""")
  

Create a primary key column on an existing table:

mycursor.execute("ALTER TABLE customers ADD COLUMN id INT AUTO_INCREMENT PRIMARY KEY")
  

7) Insert Data

Insert one row (parameterized)
# skip the steps to define mydb and mycursor

sql = "INSERT INTO customers (name, address) VALUES (%s, %s)"
val = ("John", "Highway 21")
mycursor.execute(sql, val)

mydb.commit()
print(mycursor.rowcount, "record inserted.")
  

About the “Unread result found” error

This usually happens when a previous statement produced a result set that wasn’t fully fetched, and then you executed another statement using the same connection/cursor.

# Example that can trigger the issue if results are not consumed:

mycursor.execute("SHOW TABLES")  # returns rows

sql = "INSERT INTO customers (name, address) VALUES (%s, %s)"
val = ("John", "Highway 21")
mycursor.execute(sql, val)       # can fail if SHOW TABLES results not consumed
mydb.commit()
    
# One simple fix: consume results before running another statement

mycursor.execute("SHOW TABLES")
results = mycursor.fetchall()
for row in results:
  print(row)

sql = "INSERT INTO customers (name, address) VALUES (%s, %s)"
val = ("John", "Highway 21")
mycursor.execute(sql, val)
mydb.commit()
    
Alternative fixes
  • Use cursor(buffered=True) for small result sets.
  • Use separate cursors for separate tasks (read cursor vs write cursor).

Insert multiple rows

# skip the steps to define mydb and mycursor

sql = "INSERT INTO customers (name, address) VALUES (%s, %s)"
val = [
  ('Peter', 'Lowstreet 4'),
  ('Amy', 'Apple st 652'),
  ('Hannah', 'Mountain 21'),
  ('Michael', 'Valley 345'),
  ('Sandy', 'Ocean blvd 2'),
  ('Betty', 'Green Grass 1'),
  ('Richard', 'Sky st 331'),
  ('Susan', 'One way 98'),
  ('Vicky', 'Yellow Garden 2'),
  ('Ben', 'Park Lane 38'),
  ('William', 'Central st 954'),
  ('Chuck', 'Main Road 989'),
  ('Viola', 'Sideway 1633')
]

mycursor.executemany(sql, val)
mydb.commit()

print(mycursor.rowcount, "was inserted.")
  

Get the inserted row id:

sql = "INSERT INTO customers (name, address) VALUES (%s, %s)"
val = ("Michelle", "Blue Village")
mycursor.execute(sql, val)
mydb.commit()

print("1 record inserted, ID:", mycursor.lastrowid)
  

8) Select Data

🔎 SELECT + fetch results

Select all records:

mycursor.execute("SELECT * FROM customers")
myresult = mycursor.fetchall()
for x in myresult:
  print(x)
  

Select specific columns:

mycursor.execute("SELECT name, address FROM customers")
myresult = mycursor.fetchall()
for x in myresult:
  print(x)
  

Fetch only one row:

mycursor.execute("SELECT * FROM customers")
myresult = mycursor.fetchone()
print(myresult)
  

Combine with WHERE / ORDER BY / LIMIT

sql = "SELECT * FROM customers WHERE address = %s"
mycursor.execute(sql, ("Park Lane 38",))

sql = "SELECT * FROM customers ORDER BY name"
mycursor.execute(sql)

mycursor.execute("SELECT * FROM customers LIMIT 5")
  
Security note: always parameterize inputs

Don’t build SQL by string concatenation with user input (SQL injection risk). Use placeholders (%s) + parameters as shown above.

9) UPDATE / DELETE / DROP

🧹 Write operations require COMMIT
DELETE
sql = "DELETE FROM customers WHERE address = %s"
mycursor.execute(sql, ("Mountain 21",))
mydb.commit()
print(mycursor.rowcount, "record(s) deleted")
  
UPDATE
sql = "UPDATE customers SET address = %s WHERE address = %s"
mycursor.execute(sql, ("Canyon 123", "Valley 345"))
mydb.commit()
print(mycursor.rowcount, "record(s) affected")
  
DROP TABLE
mycursor.execute("DROP TABLE customers")
mycursor.execute("DROP TABLE IF EXISTS customers")
  

10) JOIN Tables

🔗 Join tables and read combined results

Make sure users and products tables exist and are populated.

sql = """
SELECT
  users.name AS user,
  products.name AS favorite
FROM users
INNER JOIN products ON users.fav = products.id
"""

# or LEFT JOIN / RIGHT JOIN
# sql = """
# SELECT users.name AS user, products.name AS favorite
# FROM users
# LEFT JOIN products ON users.fav = products.id
# """

mycursor.execute(sql)
myresult = mycursor.fetchall()
for x in myresult:
  print(x)
  
🎥 Further learning: SQL With Python

SQL With Python

MySQL with Java

Java connects to MySQL through JDBC (Java Database Connectivity). In practice, you add the MySQL Connector/J driver to your project, open a connection using a JDBC URL, and then run SQL with PreparedStatement (recommended) to avoid SQL injection and handle parameters safely.

Driver
MySQL Connector/J
The official MySQL JDBC driver. Modern driver class name: com.mysql.cj.jdbc.Driver (you usually don’t need to call Class.forName anymore).
Best practice
Use PreparedStatement
Avoid building SQL by string concatenation. Use placeholders ? and bind parameters.

1) Add the JDBC driver (Maven / Gradle)

Maven (add inside <dependencies>):

<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <!-- Use the latest stable version from Maven Central -->
</dependency>

Gradle:

dependencies {
  implementation("com.mysql:mysql-connector-j")
  // Use the latest stable version from Maven Central
}

2) Connection settings

JDBC URL (typical)
jdbc:mysql://HOST:3306/DB?useSSL=true&serverTimezone=UTC
Credentials
Use a least-privilege MySQL user (not root) for apps.
Common connection pitfalls
  • Timezone errors: If you see timezone-related exceptions, add serverTimezone=UTC (or your preferred timezone) to the JDBC URL.
  • SSL warnings: Modern MySQL versions may prefer SSL. Use useSSL=true (or configure properly).
  • Public Key Retrieval: For some setups you may need allowPublicKeyRetrieval=true (dev only; understand the security implications).

3) Minimal “test connection” example

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DbConnectTest {
  public static void main(String[] args) {
    String url = "jdbc:mysql://localhost:3306/StudentsInfo?useSSL=true&serverTimezone=UTC";
    String user = "yourusername";
    String password = "yourpassword";

    try (Connection conn = DriverManager.getConnection(url, user, password)) {
      System.out.println("✅ Connected to MySQL successfully!");
    } catch (SQLException e) {
      System.out.println("❌ Connection failed:");
      e.printStackTrace();
    }
  }
}

4) Query with PreparedStatement (recommended)

import java.sql.*;

public class SelectExample {
  public static void main(String[] args) throws Exception {
    String url = "jdbc:mysql://localhost:3306/StudentsInfo?useSSL=true&serverTimezone=UTC";
    String user = "yourusername";
    String password = "yourpassword";

    String sql = "SELECT StudentID, StudentName, Country, Fees FROM Infostudents WHERE Country = ? AND Fees >= ?";

    try (Connection conn = DriverManager.getConnection(url, user, password);
         PreparedStatement ps = conn.prepareStatement(sql)) {

      ps.setString(1, "India");
      ps.setInt(2, 30000);

      try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
          int id = rs.getInt("StudentID");
          String name = rs.getString("StudentName");
          String country = rs.getString("Country");
          int fees = rs.getInt("Fees");
          System.out.printf("%d | %s | %s | %d%n", id, name, country, fees);
        }
      }
    }
  }
}

5) Insert / Update / Delete (with transactions)

import java.sql.*;

public class TransactionExample {
  public static void main(String[] args) throws Exception {
    String url = "jdbc:mysql://localhost:3306/StudentsInfo?useSSL=true&serverTimezone=UTC";
    String user = "yourusername";
    String password = "yourpassword";

    String insertSql = "INSERT INTO Infostudents (StudentID, StudentName, ParentName, Address, City, PostalCode, Country, Fees) " +
                       "VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
    String updateSql = "UPDATE Infostudents SET Fees = Fees + ? WHERE StudentID = ?";
    String deleteSql = "DELETE FROM Infostudents WHERE StudentID = ?";

    try (Connection conn = DriverManager.getConnection(url, user, password)) {
      conn.setAutoCommit(false); // start transaction

      try (PreparedStatement ins = conn.prepareStatement(insertSql);
           PreparedStatement upd = conn.prepareStatement(updateSql);
           PreparedStatement del = conn.prepareStatement(deleteSql)) {

        // INSERT
        ins.setInt(1, 6);
        ins.setString(2, "Sanjana");
        ins.setString(3, "Jagannath");
        ins.setString(4, "Banjara Hills");
        ins.setString(5, "Hyderabad");
        ins.setString(6, "500046");
        ins.setString(7, "India");
        ins.setInt(8, 32000);
        ins.executeUpdate();

        // UPDATE
        upd.setInt(1, 1000);
        upd.setInt(2, 6);
        upd.executeUpdate();

        // DELETE (example)
        del.setInt(1, 7);
        del.executeUpdate();

        conn.commit();
        System.out.println("✅ Transaction committed.");
      } catch (SQLException ex) {
        conn.rollback();
        System.out.println("❌ Transaction rolled back.");
        throw ex;
      }
    }
  }
}
🧠 Good habits (industry-style)
  • Always use PreparedStatement for user inputs (SQL injection defense).
  • Use try-with-resources to auto-close Connection, Statement, and ResultSet.
  • Keep DB users least-privilege (only the permissions you need).
  • For real apps, consider a connection pool (e.g., HikariCP) instead of opening a new connection each time.
🎥 Further learning: Java JDBC tutorial

Java JDBC tutorial

Summary

  • Explain SQL as a declarative language (“what” not “how”), and recognize the four families: DDL/DML/DCL/TCL.
  • Connect to MySQL Server via CLI or Workbench and know the core connection parameters (host, port, user, password).
  • DDL: create/drop databases and tables; add/modify columns; use constraints (PK/FK/UNIQUE/NOT NULL/CHECK/DEFAULT) and indexes.
  • DML: insert/update/delete/select; filter with WHERE; group with GROUP BY; filter groups with HAVING; sort/limit results.
  • JOINs: write INNER/LEFT/RIGHT joins; understand the MySQL workaround for FULL OUTER JOIN using UNION.
  • DCL: create users; grant/revoke privileges; inspect permissions with SHOW GRANTS.
  • TCL: use START TRANSACTION, COMMIT, ROLLBACK, SAVEPOINT; understand ACID at a high level.
  • Security awareness: understand why LOAD DATA LOCAL INFILE is powerful but risky, and enable it only when needed.

© Weitian Tong. All rights reserved.